Skip to content

[OMNIML-6079] [20/21] Add the Kimi-K3 expert-parallel adapter - #2749

Open
shengliangxu wants to merge 18 commits into
shengliangx/hfep-11b-kimi-k25-vlfrom
shengliangx/hfep-11c-kimi-k3
Open

shengliangxu wants to merge 18 commits into
shengliangx/hfep-11b-kimi-k25-vlfrom
shengliangx/hfep-11c-kimi-k3

Conversation

@shengliangxu

@shengliangxu shengliangxu commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Type of change: New feature

Kimi-K3's text backbone, kimi_linear, has no native transformers modeling to swap in, so the adapter patches the checkpoint's remote code in place:

  • It replaces the MoE block's per-expert ModuleList with fused experts that dispatch through the experts interface, keeping K3's latent expert space and its custom SwiGLU.
  • It registers converters that fuse the checkpoint's per-expert w1/w3 into gate_up_proj and stack w2 into down_proj. The MXFP4 experts are decompressed as they load ([18/21]).
  • It registers torch_a2a, the experts implementation it names on the text config, with transformers before the plan decision ([5/21]) builds the model on the meta device. transformers refuses to build a model whose experts implementation it has not registered, and the expert-parallel load registers it only afterwards, so before this every multi-rank Kimi-K3 run failed while deciding its plan.
  • It fixes the parts of the remote model that the expert-parallel load and calibration trip over:
    • KDA's A_log shape, and its sigmoid(beta), which fla-core silently skips; real weights then blow the recurrent state up to inf. Remote code whose KDA layers lack chunk_kda and fused_recurrent_kda to wrap is refused instead of loading with the raw gate, and so is a kernel call whose beta the wrapper cannot find;
    • the causal-mask and cache signatures, and the query offset transformers 5.15 asks the cache for, which the remote cache predates (generate stopped at its first masked forward with a cache);
    • the kwargs newer transformers pass to tie_weights.
  • It keeps K3's latent-expert projections (routed_expert_down_proj / routed_expert_up_proj) and its scalar residual gates (self_attention_res_proj, mlp_res_proj, output_attn_res_proj) unquantized in every built-in format.
    • vLLM builds them unquantized and refuses a checkpoint that quantizes them.
    • The checkpoint-mirror recipe behind nvidia/Kimi-K3-NVFP4 already keeps them in BF16.
  • It adds Kimi's mm_projector to the default unit's vision block.
    • That block is meant to keep every multimodal projector in BF16, but it named only gemma's and Llama-4's spellings, so --recipe runs, which keep the whole VLM, quantized Kimi's.
    • The published Kimi-K2.5 and Kimi-K3 NVFP4 checkpoints keep it in BF16.

It needs transformers 5.13, the release it was developed and validated against; its spec declares it, so an older release is refused before prepare runs. Its spec states the plan prepare attaches, as [19/21]'s do, so a run at an EP degree of 1 still finds its experts ([13/21]).

The adapter is over the ~500-line budget (~620 lines) because it is a self-contained model drop with no working intermediate state. Every patch is needed before K3 loads expert-parallel at all, and about a third of the lines are the docstrings that record why each patch exists.

Usage

Picked automatically by apply_distributed_support for kimi_k3 configs under torchrun; see [21/21].

Testing

  • New tests/unit/torch/models/test_kimi_k3_ep.py (+811 lines), against a stand-in remote module that follows the checkpoint's own code, covering:
    • the fused experts through the experts interface;
    • the patched MoE block against the remote one, with latent and plain experts and with the situ activation. The block derives latent MoE from routed_expert_hidden_size, as the checkpoint's modeling does; the config has no use_latent_moe field;
    • each KDA, causal-mask, cache and tie_weights shim, including a beta passed positionally and the cache's query offset, added only to a cache without one. The stand-in kernels take use_beta_sigmoid_in_kernel through **kwargs, as fla-core 0.5's do;
    • the w1/w3/w2 conversions;
    • the plan naming exactly the fused params;
    • prepare patching the defining module once, failing loudly when none defines the MoE block, and refusing a call without the checkpoint's path before it changes anything;
    • the causal-mask shim passing every keyword to a signature that takes **kwargs, and letting any keyword but cache_position that the signature rejects raise;
    • KDA layers without the kernels to wrap refused;
    • the plan prepare attaches equal to the one the spec states;
    • prepare leaving the loader alone for bf16 experts, which now runs without compressed-tensors installed;
    • the conversions' registration raising when transformers' conversion API is missing. Before, it only warned, and the experts loaded as missing;
    • a model built on the meta device from the prepared config, in a process that has not registered torch_a2a, passing transformers' experts-implementation check. Without the registration it fails with the experts_implementation="torch_a2a" is not supported error that a tiny Kimi-K3 on 2 GPUs (transformers 5.18) hit while deciding its plan;
    • a KDA kernel asked for the in-kernel sigmoid whose beta the wrapper cannot find (a signature hiding it behind *args) refused rather than run on the raw gate (fails without the fix).
  • tests/unit/torch/utils/test_resolve_distribution_plan.py: a remote-code Kimi-K3 left whole on every rank at an EP degree of 1 is refused, as [19/21]'s Kimi models are.
  • tests/unit/recipe/test_presets.py: the fp8 and nvfp4 presets leave those five projections and mm_projector unquantized.
  • Mutation-checked: each test fails on at least one deliberate break of the code.
  • Each checkpoint revision loaded in one process patches its own remote module, found in the package of the class its auto_map loads.
  • Duplicates fold into the tests that cover them: the per-expert reference into the patched-block test, the two beta tests into one, the mm_projector preset into the Kimi projections preset. Each drop is checked by mutation.
  • transformers 5.12 is refused for Kimi-K3 by its spec.
  • Locally on this revision, the stack rebased on main 0638a27: pre-commit over the whole PR against main, at every PR of the stack; each fix's own tests at its PR; and at the stack's tip the unit tests of the changed areas (tests/unit/torch/utils, export, models, the fused-experts quantization tests and the hf_ptq example tests) on transformers 5.18 and 5.5 and on torch 2.8. They pass apart from the diffusers-export and GGML tests, which fail the same way on main in that environment. CI runs the full unit suite.
  • The GPU results here were measured on earlier revisions of this branch, except one run on this revision: a tiny Kimi-K3 (bf16, 4 layers, 32 experts, KDA and full-attention layers) on 2 GPUs at EP 2 with the dense part replicated, transformers 5.18 and fla-core 0.5.0, --qformat nvfp4 --kv_cache_qformat fp8, max calibration on 64 samples of the default text blend (sequence length 128, batch 2). It decided its plan, previewed before and after PTQ on both ranks, calibrated in 51 s and exported, in 4 min 28 s at 13.9 GB per GPU. Before this revision it failed while deciding its plan (torch_a2a not registered), and with that fixed, in generate (the cache's query offset). The rest of this revision's GPU tests run in CI only.
  • On GPUs (16 GB200 nodes, EP64, dense FSDP2-sharded), Kimi-K3 NVFP4 loads and calibrates with no NaN, and its export checks out against the source checkpoint:
    • all 92 router correction biases match the checkpoint, to BF16 rounding of its F32 values;

    • routed experts export as experts.N.w1/w2/w3 and shared experts as gate_proj/up_proj/down_proj, the naming vLLM 0.28's Kimi-K3 loader reads;

    • the latent-expert projections, the residual gates and mm_projector (373 modules) come out BF16, matching nvidia/Kimi-K3-NVFP4;

      • every other weight and weight scale is byte-identical to the export made before this change, in 120 hashed tensors;
      • only calibration-derived input scales vary, within run-to-run noise;
    • vLLM 0.28 loads the export and starts serving at TP=32. It cannot serve one with an FP8 KV cache, however:

      • its Kimi-K3 FP8-KV path needs an FP8 prefill query, which its NVFP4 linears reject;
      • nvidia/Kimi-K3-NVFP4 ships without a quantized KV cache.

      TBD: generation from the checkpoint-mirror recipe models/moonshotai/Kimi-K3/ptq/nvfp4_experts-fp8_pb_attention, with [3/21]'s expert weight-scale fix.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ✅ Additive; runs only on the expert-parallel path.
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A. flash-linear-attention is a runtime requirement of the checkpoint's own remote code, not of ModelOpt.
  • Did you write any new necessary tests?: ✅
  • Did you update Changelog?: N/A. The feature's entry lands with [21/21].
  • Did you get Claude approval on this PR?: ❌ Not yet.

Additional Information

Part 20 of 21 of expert-parallel PTQ for Hugging Face MoE models; merges after [19/21].

Summary by CodeRabbit

  • New Features
    • Added support for running Kimi-K3 models with expert parallelism, including supported remote model implementations.
    • Updated FP8 and NVFP4 quantization presets for Kimi-K3. Selected projections and multimodal components remain unquantized by default, while other layers can still be quantized.
  • Compatibility
    • Kimi-K3 checkpoint support requires Transformers 5.8 or later. Loading checkpoint-specific remote code requires explicitly enabling trusted remote code.
  • Bug Fixes
    • Improved compatibility with compressed model weights and remote-model loading.

@copy-pr-bot

copy-pr-bot Bot commented Oct 12, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
Walkthrough

This change registers Kimi-K3 remote-code modeling and adds an expert-parallel adapter with fused experts, checkpoint weight converters, and remote API compatibility patches. It also updates default-disabled quantizer rules for Kimi-K3 projections and adds adapter and preset tests.

Changes: Kimi-K3 expert parallelism

Layer / File(s) Summary
Fused expert execution and weight conversion
modelopt/torch/models/kimi_k3/ep.py, tests/unit/torch/models/test_kimi_k3_ep.py
The adapter defines the expert-parallel plan, replaces per-expert modules with fused experts, and registers checkpoint converters. Tests validate expert execution and converted weight targets.
Remote model compatibility
modelopt/torch/models/kimi_k3/ep.py, tests/unit/torch/models/test_kimi_k3_ep.py
The adapter adds wrappers for KDA functions, causal-mask arguments, cache mask sizing, and tie_weights. Tests cover the wrapper behavior.
Preparation and model registration
modelopt/torch/models/kimi_k3/*, modelopt/torch/models/__init__.py, tests/unit/torch/models/test_kimi_k3_ep.py, tests/unit/torch/utils/test_resolve_distribution_plan.py
The preparation function loads and patches remote model classes, configures expert parallelism and conversions, and handles quantization configuration. The model package imports the spec that selects the preparation function. Tests cover preparation, loading, failure cases, and distribution-plan handling.

Kimi-K3 quantizer defaults

Layer / File(s) Summary
Quantizer rules and preset checks
modelopt_recipes/configs/ptq/units/default_disabled_quantizers.yaml, tests/unit/recipe/test_presets.py
Default-disabled rules cover specified routed-expert, residual, and mm_projector layers. FP8 and NVFP4 tests check these placements and verify that self_attn.o_proj remains quantized.

Priority: Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant ModelSpec
  participant Prepare
  participant RemoteModel
  ModelSpec->>Prepare: select Kimi-K3 preparation
  Prepare->>RemoteModel: load and patch remote classes
  Prepare->>Prepare: configure expert-parallel plan and conversions
Loading

Possibly related issues

  • NVIDIA/Megatron-LM#6757: The issue describes planned Kimi-K3 MoE support and expert parallelism; this change adds Kimi-K3 expert-parallel support.
  • NVIDIA/Megatron-LM#8427: The issue describes routed-only MegaMoE extensions, latent paths, and expert-parallel integration; this change adds expert-parallel support with latent-expert handling.
Merge Risk: Low · up to 78dad

A malformed compressed checkpoint configuration could pass preparation without the required decompression setup, risking incorrect expert loading. Narrow the exception handling before merging.

Pre-merge checks: Passed 5 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 47.62% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 84 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 5 passed
Check name Status Explanation
Title check Passed The title "[OMNIML-6079] [20/21] Add the Kimi-K3 expert-parallel adapter" matches the main change. The PR adds an expert-parallel adapter for Kimi-K3 (ep.py, specs.py, package init). The ticket and se…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns Passed No listed security anti-pattern is introduced in the changed ModelOpt Python code. prepare exposes trust_remote_code as a caller-configurable parameter defaulting to False and passes that variab…
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Full details: Title check

Explanation

The title "[OMNIML-6079] [20/21] Add the Kimi-K3 expert-parallel adapter" matches the main change. The PR adds an expert-parallel adapter for Kimi-K3 (ep.py, specs.py, package init). The ticket and series markers are noise, but the core change is clear and specific.

Full details: Security Anti-Patterns

Explanation

No listed security anti-pattern is introduced in the changed ModelOpt Python code. prepare exposes trust_remote_code as a caller-configurable parameter defaulting to False and passes that variable to get_class_from_dynamic_module; it does not hardcode trust_remote_code=True. The diff adds no torch.load(..., weights_only=False), numpy.load(..., allow_pickle=True), external-input eval/exec, or # nosec comments. No dependency manifest changes were made.

✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@shengliangxu
shengliangxu added this pull request to stack #2755 October 12, 2026 00:57
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-11c-kimi-k3 branch from 660d339 to 784e815 Compare October 12, 2026 02:44
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://NVIDIA.github.io/Model-Optimizer/pr-preview/pr-2749/

Built to branch gh-pages at 2026-10-25 21:32 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 784e815

@codecov

codecov Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.41085% with 17 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (shengliangx/hfep-11b-kimi-k25-vl@e5408a8). Learn more about missing BASE report.

Files with missing lines Patch % Lines
modelopt/torch/models/kimi_k3/ep.py 93.28% 17 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                         @@
##             shengliangx/hfep-11b-kimi-k25-vl    #2749   +/-   ##
===================================================================
  Coverage                                    ?   62.49%           
===================================================================
  Files                                       ?      668           
  Lines                                       ?    74334           
  Branches                                    ?        0           
===================================================================
  Hits                                        ?    46452           
  Misses                                      ?    27882           
  Partials                                    ?        0           
Flag Coverage Δ
unit 62.49% <93.41%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-11c-kimi-k3 branch from 784e815 to 57494b6 Compare October 12, 2026 08:39
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 57494b6

@shengliangxu shengliangxu changed the title [14/19] Add the Kimi-K3 expert-parallel adapter [OMNIML-6079] [14/19] Add the Kimi-K3 expert-parallel adapter Oct 12, 2026
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-11c-kimi-k3 branch from 57494b6 to 25176cb Compare October 13, 2026 00:53
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-11c-kimi-k3 branch from 25176cb to 53b85aa Compare October 13, 2026 01:32
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 53b85aa

@shengliangxu
shengliangxu removed this pull request from stack #2755 October 13, 2026 17:42
@shengliangxu
shengliangxu added this pull request to stack #2775 October 13, 2026 17:43
@shengliangxu shengliangxu changed the title [OMNIML-6079] [14/19] Add the Kimi-K3 expert-parallel adapter [OMNIML-6079] [14/20] Add the Kimi-K3 expert-parallel adapter Oct 13, 2026
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-11c-kimi-k3 branch from 53b85aa to ba6939d Compare October 13, 2026 19:38
@shengliangxu
shengliangxu removed this pull request from stack #2775 October 13, 2026 19:38
@shengliangxu
shengliangxu added this pull request to stack #2781 October 13, 2026 19:40
@shengliangxu shengliangxu changed the title [OMNIML-6079] [14/20] Add the Kimi-K3 expert-parallel adapter [OMNIML-6079] [16/21] Add the Kimi-K3 expert-parallel adapter Oct 13, 2026
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test ba6939d

@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-11c-kimi-k3 branch from ba6939d to dec8f8c Compare October 13, 2026 21:12
@shengliangxu
shengliangxu removed this pull request from stack #2781 October 14, 2026 00:13
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-11c-kimi-k3 branch from dec8f8c to 954d8bf Compare October 14, 2026 00:14
Comment thread modelopt/torch/models/kimi_k3/ep.py
Comment thread modelopt/torch/models/kimi_k3/ep.py

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review — [20/21] Kimi-K3 expert-parallel adapter (round 4)

Scope: full review (the trigger comment carried no scoping instructions). 9 changed files (+1653/-4), all opened, in modelopt/ → modelopt_recipes/ → tests/ order. For context I re-read utils/moe.py (register_torch_a2a, check_distributed_support, apply_distributed_support, torch_a2a_experts_forward), model_load_utils.py (resolve_distribution_plan, _decide_distribution, the three apply_distributed_support call sites, _make_expert_param_matcher), _onfly_decompress.install_onfly_decompress, and quantization/plugins/huggingface.py (_running_grouped_mm, _local_experts, normalize_experts_impl_on_the_fly).

Prior rounds

Rounds 1–3 raised 2 IMPORTANT and 14 SUGGESTION findings; round 3 approved. Spot-checking the ones with code to check against the current tip (9df995b1): the ckpt_path is None refusal (ep.py:593), the live sentinel guard (num_experts + 1, ep.py:215), the VAR_KEYWORD handling plus the explicit cache_position-only drop-list (ep.py:406-419), reshape in the block forward, requires_transformers="5.13" on the spec, the loud raise when no KDA kernel is found (ep.py:338), the loud import of the conversion API (ep.py:540), and the per-checkpoint remote-module discovery (_find_module_defining, replacing the process-global _PATCHED flag Codex flagged) are all in place. CodeRabbit's install_onfly_decompress finding is addressed too (ep.py:641 gates on quantization_config without swallowing validation errors).

New findings this round

CRITICAL: 0 · IMPORTANT: 1 · SUGGESTION: 2

IMPORTANT

  1. _patch_kda_beta_sigmoid's _wrapped can still pass use_beta_sigmoid_in_kernel=True through with the raw gate (ep.py:359-371). Three fall-throughs leave the flag set and beta unsquashed — beta_pos is None (unintrospectable signature, or fla-core renaming the parameter while the remote code passes it positionally), a non-tensor beta keyword with beta_pos >= len(args), and a non-tensor args[beta_pos]. Nothing pins fla-core: the docstring names 0.5.0 but no floor enforces it, and the spec's transformers floor does not cover it. In each case fla-core ignores the flag, the delta-rule recurrence diverges to inf, and the only symptom is a NaN amax after a full EP64 calibration. This is the same argument you accepted one round ago for the sibling "no kernels to wrap" case, applied one level in: the kernel was found, but the gate it was wrapped for was not. Fix sketched in the thread.

SUGGESTION

  1. prepare's side effects are process-global, but resolve_distribution_plan returns the raw config when the plan turns out non-EP (model_load_utils.py:1595) — fused block patched and converters registered, yet that config is neither marked for the decompressor nor stripped of quantization_config, so the fused experts would load missing. The ep_size == 1 and dense == "replicate" refusal at :1446 does not cover dense == "fsdp" with ep_size == 1, nor the early auto_class return at :1368. Family-wide (kimi_k25 / kimi_vl share it), so not a blocker — but Kimi-K3 is the first adapter that patches the checkpoint's own class and leaves it in place.
  2. hasattr(Cache, "get_query_offset") reads through the MRO, so an inherited (and, for this hybrid cache, non-working) implementation suppresses the new shim; Cache.__dict__ is the test _shim_tie_weights_kwargs already uses for tie_weights (ep.py:452-463).

What checked out

  • Fused layout and the a2a contract. gate_up_proj [E, 2I, H_latent] / down_proj [E, H_latent, I] with is_transposed=False, has_gate=True, has_bias=False is what torch_a2a_experts_forward reads (moe.py:446-508); _apply_gate's chunk(2, dim=-1) split matches the converters' MergeModulelist(dim=0) + Concatenate(dim=1) row order (w1=gate first, w3=up second), and SituAndMul taking the full concat matches the per-expert reference.
  • The new register_torch_a2a() in prepare (ep.py:630) lands before _experts_implementation is named and before the plan decision's meta build, which is the ordering the "torch_a2a is not supported" failure needed. It only runs on the EP path (model_load_utils.py:1571 gates prepare on world_size > 1 and _ep_degree(...) != 1), the registry writes are additive and idempotent, and load_and_prepare_ep registering again is a no-op. The utils/moe.py docstring edit is the whole code change there.
  • Latent-MoE ordering in the rewritten _forward: down_proj → fused experts → optional norm → up_proj → reshape(orig_shape) → shared experts on the un-flattened identity, with use_latent_moe read off the block rather than a config field the checkpoint lacks. Matches the stand-in's per-expert reference, which the test compares against.
  • get_mask_sizes / get_query_offset consistency: (ql + past_seen, 0) and past_seen are exactly what transformers' own DynamicCache returns, so the two agree on kv_offset.
  • Conversion registration by model_type only (not class names), with the de-duplication reasoning for the nested kimi_linear VLM scopes; overwrite=True is safe for a non-native model type.
  • Decompressor ordering and idempotence: converters registered before install_onfly_decompress patches get_model_conversion_mapping, quantization_config dropped after, and a re-prepare of the same checkpoint hits the installed is not None / compression is None early returns at _onfly_decompress.py:337-351 rather than re-installing.
  • Preset exclusions: the five Kimi-K3 projection spellings and mm_projector are additive enable: false entries in the shared default unit, consistent with how gemma's and Llama-4's projector spellings already live there; recipes can still re-enable them. The new parametrized test asserts both the exclusions and that self_attn.o_proj stays quantized, so it would fail on an over-broad glob.
  • Spec wiring: prepared_ep_plan=BASE_MODEL_EP_PLAN with no base_model_ep_plan/plan_config_attr mirrors kimi_k25, so apply_distributed_support attaches nothing itself and _spec_ep_plan still sees the plan — which is what makes the new EP-degree-1 refusal test pass.
  • Tests: the stand-in follows the checkpoint's own shapes (stale A_log as [num_heads], get_mask_sizes wanting a tensor, tie_weights with the old signature, fla-core 0.5 kernels taking the flag through **kwargs), and the query-offset test covers both the add and the leave-alone branch. SituAndMul holding plain floats confirms the fused module gains no parameter the checkpoint has no key for.

Risk

Moderate, and well bounded. The adapter is additive, runs only on the expert-parallel path, and is backed by both unit tests and a real EP64 load whose export is byte-compared against the previous one. The one IMPORTANT is a defensive gap in a defensive patch; neither SUGGESTION blocks. Note the PR body's own ~620 lines figure is now ~646 for ep.py alone (~700 source across the three new files) — the self-contained-model-drop exemption in CONTRIBUTING covers it, but the number in the description is stale.

🤖 Generated with Claude Code

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 94b51bf

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/claude review

Comment thread modelopt/torch/models/kimi_k3/ep.py
Comment on lines +38 to +39

if Version(transformers.__version__) < Version("5.8"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION] The module skips below transformers 5.8, but the adapter's own floor is 5.13 (specs.py: requires_transformers="5.13", enforced by check_distributed_support before prepare runs), and the commit that added this gate is titled "Skip the tests below the transformers release the code requires". 5.8 is four minors below that release.

Why it matters: it leaves 5.8–5.12 as a band these tests run in but nobody has exercised — Testing lists 5.18 and 5.5, and 5.5 is skipped by this very gate. In that band the module-level from transformers.core_model_loading import WeightConverter, build_glob_alternation and the cm.get_checkpoint_conversion_mapping / USER_REGISTERED_MAPPINGS uses are the exposure: the private_conversion_registry fixture already treats USER_REGISTERED_MAPPINGS as 5.13+, so the registry semantics these tests assert on are known to differ there. A missing symbol would be a collection error, not a skip, so it fails the whole file rather than one test.

Raising the gate to the release the adapter declares makes the tested range exactly the supported one:

Suggested change
if Version(transformers.__version__) < Version("5.8"):
if Version(transformers.__version__) < Version("5.13"):
pytest.skip("Kimi-K3 expert parallelism needs transformers >= 5.13", allow_module_level=True)

If 5.8 was chosen deliberately — the helpers run against stand-ins, so most of them are version-independent, and 5.8 is install_onfly_decompress's own floor — a one-line comment saying so would keep the next reader from "fixing" it to match the spec.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Postponed: the 5.8 gate was this module's floor when it was set (cc5e1cb, when prepare itself required 5.8), and 6e5739a moved the declared floor to the spec's 5.13 without raising it. Nothing in the band is broken: on transformers 5.12.0 the module collects and all 29 tests pass, and the registry fixture already handles the pre-5.13 registry. Aligning the gate and its reason with the spec, as the Kimi-VL and Kimi-K2.5 test files do, is postponed to a follow-up rather than reopening this approved slice.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review — [20/21] Kimi-K3 expert-parallel adapter (second pass)

Scope applied: full review (the trigger comment carried no scoping instructions). 9 changed files, 1674 additions — all reviewed: modelopt/torch/models/kimi_k3/{ep,specs,__init__}.py, modelopt/torch/models/__init__.py, modelopt/torch/utils/moe.py, modelopt_recipes/configs/ptq/units/default_disabled_quantizers.yaml, and the three test files. Read for context: utils/moe.py (check_distributed_support, apply_distributed_support, _spec_ep_plan, register_torch_a2a, _SUB_CONFIG_ATTRS), _onfly_decompress.install_onfly_decompress, plugins/model_load_utils.py (the three apply_distributed_support call sites + resolve_distribution_plan), quantization/plugins/huggingface.py (normalize_experts_impl_on_the_fly), models/specs.py.

Findings — CRITICAL: 0 · IMPORTANT: 0 · SUGGESTION: 2 new (both non-blocking)

# Where What
1 ep.py:353-357 beta_pos indexes the full parameter list, so a keyword-only beta gets a position it can never occupy; one call shape then clobbers a neighbouring argument and fails inside the kernel instead of through this wrapper's own refusal
2 test_kimi_k3_ep.py:38 Module skip floor is 5.8 while the adapter's declared floor is 5.13, leaving 5.8–5.12 as a tested-but-unexercised band

Still open from the previous pass (not re-filed, listed for tracking): the _init(self, config) signature vs. the siblings' *args, **kwargs; the third copy of the tie_weights kwargs shim; the auto_map lookup accepting only the two text-only auto classes; and the ordering comment at ep.py:643-649. That last one is worth a second look — it now states the registration must happen "BEFORE the decompressor patches get_model_conversion_mapping", but install_onfly_decompress installs a patched wrapper that calls orig(...) at load time, so it sees whatever is registered then and the order here is free. The code is correct either way; only the comment asserts a constraint that is not there.

What I traced this round

  • The new register_torch_a2a() in prepare (the main delta). Correct and necessary. prepare names torch_a2a on text_config, and resolve_distribution_plan builds the model on meta from that config before load_and_prepare_ep registers anything — transformers rejects an unregistered experts implementation at build, which is the experts_implementation="torch_a2a" is not supported failure the description reports. The registration is idempotent via _TORCH_A2A_REGISTERED, adds only new names to ALL_EXPERTS_FUNCTIONS / ALL_PARALLEL_STYLES, and load_and_prepare_ep calls it again harmlessly. test_a_model_builds_from_the_prepared_config_before_the_expert_parallel_load reproduces the failure honestly — it clears _global_mapping and _TORCH_A2A_REGISTERED to put the process back in the state the plan decision finds, and uses a stock Qwen3-MoE because the check is transformers', not Kimi's.
  • Setting _experts_implementation in prepare does not leak to non-EP paths. All three apply_distributed_support call sites are gated on route == _EXPERT_PARALLEL, and load_and_prepare_ep:2571 only writes the attribute onto configs that already have it — which is precisely why prepare must seed it. resolve_distribution_plan discards the prepared copy when the plan is not expert-parallel (return plan, prepared[0] if plan.is_expert_parallel else hf_config), and the EP-degree-1 whole-model case is refused outright by the new test_resolve_distribution_plan parametrization rather than silently loading with a stale torch_a2a.
  • Plan placement. ep_spec leaves base_model_ep_plan unset and supplies prepared_ep_plan, so apply_distributed_support skips the declarative branch and plan_config_attr is correctly irrelevant; prepare puts the plan on text_config, which is one of the two spellings the EP load reads (_SUB_CONFIG_ATTRS = ("text_config", "llm_config")). _spec_ep_plan falls through to prepared_ep_plan, so a dense-whole run still resolves the routed experts — and the test asserts the attached plan equals the spec's, so the two cannot drift.
  • Fused layout and the gate. gate_up_proj [E, 2I, H_latent] / down_proj [E, H_latent, I] with is_transposed=False matches what torch_a2a_experts_forward indexes. The converter chain MergeModulelist(dim=0) then Concatenate(dim=1) puts w1 in rows [0:I] and w3 in [I:2I], which is the half chunk(2, dim=-1) / SituAndMul treats as the gate; has_gate=True routes through _apply_gate rather than act_fn, so the custom situ SwiGLU survives the a2a path and not just the eager fallback. The dispatch test checks this against a real transformers backend, so a layout error shows up as an eager/dispatched mismatch rather than as silent numerics.
  • Block forward equivalence, including identity being the unflattened input and reshape(orig_shape) landing after the latent up-projection, across the latent / plain / situ parametrizations. The eager fallback's one_hot(..., num_experts + 1) then permute(2,1,0) then where(mask[e]) indexing is consistent against [T, topk] weights, and the sentinel class is skipped rather than folded in.
  • The KDA beta fix, the riskiest patch here, is right in substance: sigmoid in fp32 cast back to beta.dtype, the kernel flag cleared so fla-core cannot double-apply, beta found by keyword or by position, and both failure modes refused loudly — remote code without the kernels to wrap, and now a call whose beta the wrapper cannot locate. Finding 1 is a robustness nit on the position lookup, not a correctness break: every path still raises rather than running the recurrence on the raw gate.
  • Decompressor wiring. install_onfly_decompress is now called only when a quantization_config is present, and a non-compressed-tensors one reaches its own ValueError rather than being swallowed. Re-preparing a config whose quantization_config was already dropped hits the compression is None early return; a fresh deepcopy with the config still present re-marks it. _compressedify keys off a .weight suffix under experts, so K3's w1/w3/w2 sources are rewritten, and the mark is cleared once converters are rewritten so a later save_pretrained does not inherit it.
  • Validate before mutate. Both prepare refusals (ckpt_path is None, trust_remote_code=False) fire before any config or class is touched, and check_distributed_support enforces the 5.13 floor before prepare runs at all.
  • Collective symmetry. Every patch, shim and refusal is rank-independent; resolve_distribution_plan wraps the spec application in dist.all_reduce("model spec", ..., all), so a rank that cannot apply it fails every rank instead of stranding the others in a collective.
  • Recipe change. --qformat in examples/hf_ptq/hf_ptq.py reads presets.QUANT_CFG_CHOICES, which is built from these preset YAMLs — so the new exclusions really do apply to "every built-in format", not just --recipe runs, and the new preset test exercises exactly that entry point. The five projection names and mm_projector are distinctive enough not to collide with other families, and the test's o_proj assertion would catch an over-broad glob.
  • No prompt-injection content in the diff.

Risk assessment — low-to-moderate, unchanged from the first pass and inherent rather than introduced: the adapter monkey-patches a third-party checkpoint's remote code, bounded by model_type == "kimi_k3" on the expert-parallel path, with one process-wide side effect (register_checkpoint_conversion_mapping("kimi_linear", ...)) scoped to a model type nothing else in the repo claims. The standing risk is remote-code drift, and this revision has moved further toward confronting it — per-revision module lookup by defined class, a hard refusal when the KDA kernels are not the ones it knows, and now a refusal when the beta gate cannot be located. The ~620-line ep.py exceeds the ~500-line budget, which I read as covered by the "self-contained drop such as a new model/backend that has no working intermediate state" carve-out; the description says so up front.

Approving: no blocking issues.

🤖 Generated with Claude Code

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test d012fee

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test b94228e

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test d0fe659

Kimi-K3's text backbone, kimi_linear, has no native transformers modeling
to swap in, so the adapter patches the checkpoint's remote code in place:
- it replaces the MoE block's per-expert ModuleList with fused experts that
  dispatch through the experts interface, keeping K3's latent expert space
  and its custom SwiGLU;
- it registers converters that fuse the checkpoint's per-expert w1/w3 into
  gate_up_proj and stack w2 into down_proj, and has the MXFP4 experts
  decompressed as they load;
- it fixes the parts of the remote model that the expert-parallel load and
  calibration trip over: KDA's A_log shape and its sigmoid(beta), which
  fla-core silently skips, the causal-mask and cache signatures, and the
  kwargs newer transformers pass to tie_weights.
It needs transformers 5.8, for the decompression.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The experts read use_latent_moe off the config, but Kimi-K3's config has no
such field: the model derives it from routed_expert_hidden_size. So every
stock K3 config built the experts at the hidden width (7168) instead of the
latent one (3584), and the expert-parallel load failed on the size mismatch.

The block's own __init__ already decides latent MoE, and the patched forward
follows that decision, so the fused experts now take their width from the
block. The test stand-in derives the flag the same way instead of carrying a
config field the real checkpoint does not have, which is how the tests
missed this.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
fla-core's chunk_kda and fused_recurrent_kda have no use_beta_sigmoid_in_kernel
parameter: the flag lands in their **kwargs, which is why they ignore it.
Binding a call to the kernel's signature therefore filed the flag under
'kwargs', the wrapper never saw it, and beta reached the recurrence raw.
On Kimi-K3 the state overflowed and the first calibration forward produced
NaN out of the first KDA layer.

The flag is read off the call's keywords again, as before the binding;
beta is still found by name or by position. The test kernels now take the
flag through **kwargs, as fla-core's do, which is how the tests missed it.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The module skipped below 5.6, but the code under test requires 5.8 and
raises below it, so a 5.6 or 5.7 run failed instead of skipping.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…zed by default

vLLM's Kimi-K3 model builds routed_expert_down_proj, routed_expert_up_proj and the self_attention, mlp and output_attn residual gates with quant_config=None, so it refuses a checkpoint that quantizes them. The default formats quantized all 371 of them in a Kimi-K3 export. The [1, hidden] gates gain nothing from quantization either.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The default unit means to keep every multimodal embedding projection in BF16, but it names only gemma's embed_vision and Llama-4's multi_modal_projector. Kimi-K2.5 and Kimi-K3 call theirs mm_projector, so every --recipe run, which keeps the whole VLM, quantized it. The published Kimi-K2.5 and Kimi-K3 NVFP4 checkpoints both keep it BF16.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
prepare patched the remote modeling only while a process-wide flag was
unset, and found the module to patch as the first one in sys.modules that
defines the MoE block, so a second checkpoint revision in the same process
kept its per-expert layout, or had another revision's module patched in its
place. Look the modules up in the package of the class the checkpoint's
auto_map loads, and patch on every prepare: each patch is idempotent.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Kimi-K3's prepare patches the checkpoint's remote MoE block and attaches
the plan for its fused experts. An EP degree of 1 skips prepare, so its
spec supplied no plan there, and a checkpoint, whose config carries none,
loaded whole on every rank with a replicated dense part instead of being
refused. Its spec now states the plan prepare attaches, which finds the
experts the checkpoint's own code builds, at the same paths.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Its skip predates the decompressor looking up the quantization config
before it imports compressed_tensors, so a bf16 checkpoint no longer
needs the package and the test passes without it. The skip kept it from
running anywhere the package is missing, a plain dev-test install
included.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
- The per-expert reference test becomes the situ case of the
  patched-block test, which compares with the remote block itself.
- The patch-twice test goes: the revisions test fails on a
  non-idempotent block patch.
- The two beta tests become one, parametrized by how beta is passed,
  for both KDA entry points.
- The A_log test goes: the prepare-with-checkpoint test checks the
  checkpoint's shape and now the dtype it keeps.
- The tie_weights-shim-once test goes: the prepare test checks that the
  shim absorbs the kwargs and delegates exactly once, and the revisions
  test that it wraps once.
- The mm_projector preset test joins the Kimi projections preset test.
- The spec-routing test keeps only the prepare identity, which nothing
  else checks; test_specs_vs_transformers checks modeling_source.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Like the Kimi-K2.5 and Kimi-VL adapters, Kimi-K3's prepare loads the
checkpoint's own modeling through get_class_from_dynamic_module, which runs it
without checking trust_remote_code. Refuse unless the caller passed
trust_remote_code=True, before anything is staged or patched.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
When transformers' weight-conversion API failed to import,
_register_kimi_k3_conversions printed a warning and returned. The
checkpoint's per-expert weights then loaded as unexpected and the
fused experts as missing: newly initialized, at full size on every
rank. prepare() already requires transformers 5.8, where the API
exists, so the import now simply raises.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…s through

prepare suppressed every ValueError from the decompressor, meaning to skip plain bf16 experts. But
compressed_tensors raises pydantic's ValidationError, also a ValueError, for a quantization_config
it cannot parse; prepare then dropped the config and the experts would load newly initialized.
Install the decompressor only when the config carries a quantization_config, and let it raise.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…he release it was validated on

prepare refuses, before it changes anything, a call with no checkpoint path: it patches the
checkpoint's own MoE block, and without it the plan, the kimi_linear expert fusion and the
decompressor still went in, naming fused experts the unpatched per-expert modeling never builds,
whose own weights then loaded newly initialized. The trust_remote_code check covers every call.

The spec declares transformers 5.13, the release the adapter's patches were developed and validated
against, so check_distributed_support refuses an older one with its way around; prepare no longer
checks a floor itself. The create_causal_mask shim passes every keyword through to a signature that
takes **kwargs. The eager experts fallback keeps a class past the experts for the router's
no-local-expert id, as transformers' reference loop does, the block forward reshapes as the
remote one does, and the KDA patch's docstring keeps the reason, not the measurements.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The KDA beta patch returned without a word when the remote module that defines KimiDeltaAttention
had neither chunk_kda nor fused_recurrent_kda to wrap, and marked the module patched all the same:
the raw gate then reached the kernels, and the divergence surfaced only as a NaN amax after the whole
calibration. Such a module is now refused, a module is marked patched only when it has the kernels
to wrap, and a model without KDA layers is left alone.

The create_causal_mask shim dropped every keyword the installed signature rejects. It now drops only
cache_position, which transformers 5.13 no longer takes: a renamed mask input such as attention_mask
raises the remote call's TypeError instead of building a mask without the padding.

prepare's docstring now says it mutates the config it returns, and the spec's comment sits on the
transformers floor it explains.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…lds the model

Kimi-K3's prepare names torch_a2a as the text config's experts implementation, but only the
expert-parallel load registered it with transformers. resolve_distribution_plan builds the model
from that config on the meta device in between, and transformers refuses to build a model whose
experts implementation it has not registered, so every multi-rank Kimi-K3 run failed deciding its
plan with 'experts_implementation="torch_a2a" is not supported', whatever the transformers release.
prepare now registers it first.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
From transformers 5.15 the mask preprocessing asks the cache for get_query_offset(layer_idx) as well
as get_mask_sizes, and Kimi-K3's remote KimiDynamicCache predates it: generate stopped at the first
masked forward with a cache ("'KimiDynamicCache' object has no attribute 'get_query_offset'"), on
transformers 5.18 in hf_ptq's preview. Calibration was unaffected, as its forwards keep no cache.
The adapter now adds the method next to its get_mask_sizes patch, when the class has none,
answering as transformers' own caches do: the tokens already cached.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The wrapper around chunk_kda / fused_recurrent_kda applies sigmoid(beta) itself and clears the
use_beta_sigmoid_in_kernel flag fla-core ignores. When it could not find beta -- a kernel signature
that hides it, as a decorator's *args does, or beta passed some other way -- it passed the call on
with the flag set, so the kernel ran on the raw gate and the recurrence diverged to inf, a NaN amax
after a whole calibration. Such a call is now refused.

KimiDynamicCache also gets the get_query_offset shim unless the class defines its own, rather than
whenever it can reach one through a base class.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 99aa5da

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant